package com.societyGames.dotsExercises
{
import com.societyGames.action.ActionQueue;

import flash.display.Sprite;
import flash.utils.Dictionary;

public class DotsSimulation
{
  private var _sprite:Sprite = new Sprite();
  private var _render:DotsRender;
  private var _actionQueue:ActionQueue;

  public var gridWidth:int;
  public var gridHeight:int;
  public var spaceWidth:int;
  public var spaceHeight:int;
  [TypeHint(type="String")]
  public var strokes:Array;

  private var _actualStrokeCount:int;
  private var _hadExtraConnection:Boolean = false;
  private var _desiredStrokeCount:int;
  private var _desiredConnections:Dictionary = new Dictionary(); //uint to boolean

  public function get sprite():Sprite
  {
    return this._sprite;
  }

  /**
   * <initialization spaceWidth=50 spaceHeight=50 gridWidth=5 gridHeight=5>
   *   <stroke>1,3,9,19,23,21,15,5,1</stroke>
   *   <stroke>6,11</stroke>
   *   <stroke>8,13</stroke>
   *   <stroke>16,18</stroke>
   * </initialization>
   **/
  public function initialize(actionQueue:ActionQueue):void
  {
    this._actionQueue = actionQueue;

    //Render
    this._render = new DotsRender(this, this._actionQueue, spaceWidth, spaceHeight);
    this._sprite.addChild(this._render);

    //Desired Solution
    _desiredStrokeCount = 0;
    for each (var strokeText:String in strokes)
    {
      var stringDots:Array = strokeText.split(",");
      if (stringDots.length < 2)
      {
        throw new Error("Invalid stroke: " + strokeText);
      }
      for (var index:int = 1; index < stringDots.length; index++)
      {
        this._desiredConnections[pack(int(stringDots[index - 1]), int(stringDots[index]))] = false;
      }
      _desiredStrokeCount++;
    }

    //Show desired stroke count.
    this._render.setDesiredStrokeCount(this._desiredStrokeCount);
  }

  public function draw(stroke:Vector.<int>):void
  {
    this._actualStrokeCount++;
    this._render.setActualStrokeCount(this._actualStrokeCount);
    for (var index:int = 1; index < stroke.length; index++)
    {
      var value0:Object = this._desiredConnections[pack(int(stroke[index - 1]), int(stroke[index]))];
      var value1:Object = this._desiredConnections[pack(int(stroke[index]), int(stroke[index - 1]))];
      if (value0 == false || value1 == false) //First time over
      {
        this._desiredConnections[pack(stroke[index - 1], stroke[index])] = true;
        this._render.drawConnection(stroke[index - 1], stroke[index]);
      }
      else
      {
        if (value0 == true || value1 == true) //Doubling
        {
          this._render.drawConnection(stroke[index - 1], stroke[index]);
        }
        else //Invalid!
        {
          this._hadExtraConnection = true;
          this._render.drawInvalidConnection(stroke[index - 1], stroke[index]);
        }
      }
    }
  }

  public function isComplete():Boolean
  {
    if (this._actualStrokeCount > this._desiredStrokeCount) //too many strokes.
    {
      return false;
    }
    if (this._hadExtraConnection) //Extra unnneded connection.
    {
      return false;
    }
    for each (var isConnected:Boolean in this._desiredConnections)
    {
      if (!isConnected) //Missing connection.
      {
        return false;
      }
    }
    return true;
  }

  private static const MIN_VALUE:int = -32768;
  private static const MAX_VALUE:int = 32767;

  private static function pack(x:int, y:int):uint
  {
    if (x < MIN_VALUE || x > MAX_VALUE
        || y < MIN_VALUE || y > MAX_VALUE)
    {
      throw new ArgumentError("Argument is out of range: " + x + "," + y);
    }
    return (x << 16) | ((y << 16) >>> 16);
  }

  private static function unpackX(value:uint):int
  {
    return (0xFFFF0000 & value) >> 16;
  }

  private static function unpackY(value:uint):int
  {
    return ((0x0000FFFF & value) << 16) >> 16;
  }
}
}